#ifndef __ovip_third_nutshell_cache_wrapper__
#define __ovip_third_nutshell_cache_wrapper__

#include "nutshell_simplebus.h"
#include "nutshell_simpleram.h"

namespace ovip {

class CacheWrapper
{
public:
    u_int64_t mCountRead       = 0;
    u_int64_t mCountWrite      = 0;
    u_int64_t mCountReadHit    = 0;
    u_int64_t mCountWriteHit   = 0;
    u_int64_t mCountReadMiss   = 0;
    u_int64_t mCountWriteMiss  = 0;
    u_int64_t mCountReadCycle  = 0;
    u_int64_t mCountWriteCycle = 0;

    ovip::SimpleBusWrapper *p_bus    = nullptr;
    xspcomm::XPort *cache_port = nullptr;
    xspcomm::XClock *p_clk     = nullptr;
    CacheWrapper(ovip::SimpleBusWrapper *bus, xspcomm::XClock *clk,
                 xspcomm::XPort *cache_port)
    {
        this->p_bus      = bus;
        this->p_clk      = clk;
        this->cache_port = cache_port;
    }
    void ResetCount()
    {
        this->mCountRead       = 0;
        this->mCountWrite      = 0;
        this->mCountReadHit    = 0;
        this->mCountWriteHit   = 0;
        this->mCountReadMiss   = 0;
        this->mCountWriteMiss  = 0;
        this->mCountReadCycle  = 0;
        this->mCountWriteCycle = 0;
    }
    std::string StatInfo()
    {
        std::string info = "";
        info += "ReadCount:         " + xspcomm::sFmt("%d", this->mCountRead)
                + "\n";
        info += "ReadHit:           " + xspcomm::sFmt("%d", this->mCountReadHit)
                + "\n";
        info += "ReadMiss:          "
                + xspcomm::sFmt("%d", this->mCountReadMiss) + "\n";
        info += "AverageReadCycle:  "
                + xspcomm::sFmt("%.2f", (double)this->mCountReadCycle
                                            / (double)this->mCountRead)
                + "\n";
        info += "WriteCount:        " + xspcomm::sFmt("%d", this->mCountWrite)
                + "\n";
        info += "WriteHit:          "
                + xspcomm::sFmt("%d", this->mCountWriteHit) + "\n";
        info += "WriteMiss:         "
                + xspcomm::sFmt("%d", this->mCountWriteMiss) + "\n";
        info += "AverageWriteCycle: "
                + xspcomm::sFmt("%.2f", (double)this->mCountWriteCycle
                                            / (double)this->mCountWrite)
                + "\n";
        info += "ReadHitRate:       "
                + xspcomm::sFmt("%.2f%%", 100 * (double)this->mCountReadHit
                                              / (double)this->mCountRead)
                + "\n";
        info += "WriteHitRate:      "
                + xspcomm::sFmt("%.2f%%", 100 * (double)this->mCountWriteHit
                                              / (double)this->mCountWrite)
                + "\n";
        info += "TotalHitRate:      "
                + xspcomm::sFmt(
                    "%.2f%%",
                    100 * (double)(this->mCountReadHit + this->mCountWriteHit)
                        / (double)(this->mCountRead + this->mCountWrite))
                + "\n";
        return info;
    }
    void Init()
    {
        (*cache_port)["reset"] = 1;
        for (int i = 0; i < 100; i++) p_clk->Step();
        (*cache_port)["reset"] = 0;
    }
    u_int64_t Read(u_int64_t addr)
    {
        // wait ready
        p_bus->ReqUnValid();
        while (!p_bus->IsReqReady()) { p_clk->Step(); }
        // send data
        p_bus->ReqReadData(addr);
        p_bus->ReqSetValid();
        p_clk->Step();
        p_bus->ReqUnValid();
        // wait
        auto c = 1;
        while (!p_bus->IsRespValid()) {
            p_clk->Step();
            c += 1;
        }
        // statistic
        this->mCountRead += 1;
        this->mCountReadCycle += c;
        if (c <= 3) {
            this->mCountReadHit += 1;
        } else {
            this->mCountReadMiss += 1;
        }
        // read data
        return p_bus->RespData();
    }
    bool Write(u_int64_t addr, u_int64_t data, u_char mask = 0xff)
    {
        // wait ready
        p_bus->ReqUnValid();
        while (!p_bus->IsReqReady()) { p_clk->Step(); }
        // send data
        p_bus->ReqWriteData(addr, data, mask);
        p_bus->ReqSetValid();
        p_clk->Step();
        p_bus->ReqUnValid();
        // wait
        auto c = 1;
        while (!p_bus->IsRespValid()) {
            p_clk->Step();
            c += 1;
        }
        this->mCountWrite += 1;
        this->mCountWriteCycle += c;
        if (c <= 3) {
            this->mCountWriteHit += 1;
        } else {
            this->mCountWriteMiss += 1;
        }
        // check complete
        return p_bus->IsRespWriteCmp();
    }
};
} // namespace ovip
#endif
